<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        *{
            background-color: #000;
        }
    </style>
</head>
<body>

</body>
</html>
<script>
    (function(window, document, undefined) {
        // 存储所有的雪花
        const snows = [];

        // 下落的加速度
        const G = 0.01;
// 60是人眼所能见到流畅动画的最小阈值
        const fps = 60;

        // 速度上限，避免速度过快
        const SPEED_LIMIT_X = 1;
        const SPEED_LIMIT_Y = 1;

        const W = window.innerWidth;
        const H = window.innerHeight;

        let tickCount = 150;
        let ticker = 0;
        let lastTime = Date.now();
        let deltaTime = 0;

        let canvas = null;
        let ctx = null;

        let snowImage = null;

        window.requestAnimationFrame = (function() {
            return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function (callback) {
                    setTimeout(callback, 1000/ fps);
                }
        })();

        init();

        function init() {
            createCanvas();
            canvas.width = W;
            canvas.height = H;
            canvas.style.cssText = 'position: fixed; top: 0; left: 0; pointer-events: none;';
            document.body.appendChild(canvas);
            // 小屏幕时延长添加雪花时间，避免屏幕上出现太多的雪花
            if (W < 768) {
                tickCount = 350;
            }

            snowImage = new Image();
            snowImage.src = './images/snow.png';

            loop();
        }

        function loop() {
            requestAnimationFrame(loop);

            ctx.clearRect(0, 0, W, H);// 擦除当前画布内容，否则原有的雪花不会消失，新绘制的雪花不断覆盖看起来会像一条雪花白色实线在下降

            const now = Date.now();// 两个雪花之间的时间差，不能生成的太快，要不然就成了鹅毛大雪了^_
            deltaTime = now - lastTime;// 距离上一次绘制的时间差
            lastTime = now;// 重置结束时间
            ticker += deltaTime;// 时间控制器，当timer > snowLevelTime的时候，才增加雪花，否则不增加雪花
//不加控制的话雪花会特别多， 150~300之间都合适
            if (ticker > tickCount) {
                snows.push(
                    new Snow(Math.random() * W, 0, Math.random() * 5 + 5)// 绘制新雪花，x位置为随机数，y为顶部0，半径为随机数随机生成大小不一的雪
                );
                ticker %= tickCount;
            }

            const length = snows.length;
            snows.map(function(s, i) {
                s.update();
                s.draw();
                if (s.y >= H) {
                    snows.splice(i, 1);
                }
            });
        }
        /**
         * 雪花对象
         * @param {x} x
         * @param {y} y
         * @param {半径或长宽} radius
         */
        function Snow(x, y, radius) {
            this.x = x;
            this.y = y;
            this.sx = 0;// x方向的移动速度，可以向左也可以向右，范围在[-1, 1]
            this.sy = 0;// y方向的移动速度,只能向下，最快为1
            this.deg = 0;// 雪花自身旋转的角度
            this.radius = radius;// 如果是圆形就是半径，否则就是长宽相同的正方形
            this.ax = Math.random() < 0.5 ? 0.005 : -0.005;// x方向，下落速度参数，飘落效果 > 0向左飘； < 0 向右飘
        }

        Snow.prototype.update = function() {
            const deltaDeg = Math.random() * 0.6 + 0.2;// 雪花自身旋转的角度增值

            this.sx += this.ax;//// 不断变化x方向的移动速度
            // x向左或者向右速度过大的时候改变方向
            if (this.sx >= SPEED_LIMIT_X || this.sx <= -SPEED_LIMIT_X) {
                this.ax *= -1;
            }
//// 雪花下落速度，最高是1
            if (this.sy < SPEED_LIMIT_Y) {
                this.sy += G;/// 雪花下落速度不断增加
            }

            this.deg += deltaDeg;// 角度不断变化
            this.x += this.sx;// x坐标不断变化
            this.y += this.sy;// y坐标不断变化
        }

        Snow.prototype.draw = function() {
            const radius = this.radius;
            ctx.save();// 保存画布的当前状态，因为下面用到了变换坐标和旋转画布
            /**
             * 下面这两句变化也挺重要的，因为旋转是按照画布原点进行的
             * 因此，如果想让雪花旋转明显，就需要将画布坐标移动到雪花的坐标点
             * 如果不加上坐标转换，那么所有雪花都在左上角也就是坐标原点旋转，x, y也不会变，没有飘落效果
             */
// 将画布的坐标原点移动到(x, y)的位置，canvas默认是(0, 0)
            ctx.translate(this.x, this.y);
            ctx.rotate(this.deg * Math.PI / 180);// 将画布顺时针旋转的角度
            ctx.drawImage(snowImage, -radius, -radius, radius * 2, radius * 2);// 绘制雪花图像，因为画布坐标移动到了(x, y)，所以从0,0开始就是(-radius, radius)
            // 恢复canvas旋转、translate等操作的状态，一般与save配合使用就是恢复到上一个save的状态
// 如果不恢复上一个状态的话，话不旋转角度坐标都没变化，也就不会出现动画效果，必须恢复
            ctx.restore();
        }

        function createCanvas() {
            canvas = document.createElement('canvas');
            ctx = canvas.getContext('2d');
        }

    })(window, document);
</script>